/*
 * Copyright 2013-2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.cloud.nacos.parser;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import com.alibaba.cloud.nacos.utils.NacosConfigUtils;

import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.boot.env.PropertiesPropertySourceLoader;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import static com.alibaba.cloud.nacos.parser.AbstractPropertySourceLoader.DOT;

/**
 * Forked from com.alibaba.cloud.nacos.parser.NacosDataParserHandler.java
 *
 * @author zkz
 */
public final class NacosDataParserHandler {

  /**
   * default extension.
   */
  private static final String DEFAULT_EXTENSION = "properties";

  private static List<PropertySourceLoader> propertySourceLoaders;

  private NacosDataParserHandler() {
    propertySourceLoaders = SpringFactoriesLoader
        .loadFactories(PropertySourceLoader.class, getClass().getClassLoader());
  }

  /**
   * Parsing nacos configuration content.
   * @param configName name of nacos-config
   * @param configValue value from nacos-config
   * @param extension identifies the type of configValue
   * @return result of Map
   * @throws IOException thrown if there is a problem parsing config.
   */
  public List<PropertySource<?>> parseNacosData(String configName, String configValue,
      String extension) throws IOException {
    if (!StringUtils.hasLength(configValue)) {
      return Collections.emptyList();
    }
    String fileExtension = extension;
    if (!StringUtils.hasLength(extension)) {
      fileExtension = this.getFileExtension(configName);
    }
    for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) {
      if (!canLoadFileExtension(propertySourceLoader, fileExtension)) {
        continue;
      }
      NacosByteArrayResource nacosByteArrayResource;
      if (propertySourceLoader instanceof PropertiesPropertySourceLoader) {
        // PropertiesPropertySourceLoader internal is to use the ISO_8859_1,
        // the Chinese will be garbled, needs to transform into unicode.
        nacosByteArrayResource = new NacosByteArrayResource(
            NacosConfigUtils.selectiveConvertUnicode(configValue).getBytes(),
            configName);
      } else {
        nacosByteArrayResource = new NacosByteArrayResource(
            configValue.getBytes(), configName);
      }
      nacosByteArrayResource.setFilename(getFileName(configName, fileExtension));
      List<PropertySource<?>> propertySourceList = propertySourceLoader
          .load(configName, nacosByteArrayResource);
      if (CollectionUtils.isEmpty(propertySourceList)) {
        return Collections.emptyList();
      }
      return propertySourceList.stream().filter(Objects::nonNull)
          .map(propertySource -> {
            if (propertySource instanceof EnumerablePropertySource<?> enumerablePropertySource) {
              String[] propertyNames = enumerablePropertySource
                  .getPropertyNames();
              if (propertyNames.length > 0) {
                Map<String, Object> map = new LinkedHashMap<>();
                Arrays.stream(propertyNames).forEach(name -> {
                  map.put(name, propertySource.getProperty(name));
                });
                return new OriginTrackedMapPropertySource(
                    propertySource.getName(), map, true);
              }
            }
            return propertySource;
          }).collect(Collectors.toList());
    }
    return Collections.emptyList();
  }

  /**
   * check the current extension can be processed.
   * @param loader the propertySourceLoader
   * @param extension file extension
   * @return if can match extension
   */
  private boolean canLoadFileExtension(PropertySourceLoader loader, String extension) {
    return Arrays.stream(loader.getFileExtensions())
        .anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(extension,
            fileExtension));
  }

  /**
   * @param name filename
   * @return file extension, default {@code DEFAULT_EXTENSION} if don't get
   */
  public String getFileExtension(String name) {
    if (!StringUtils.hasLength(name)) {
      return DEFAULT_EXTENSION;
    }
    int idx = name.lastIndexOf(DOT);
    if (idx > 0 && idx < name.length() - 1) {
      return name.substring(idx + 1);
    }
    return DEFAULT_EXTENSION;
  }

  private String getFileName(String name, String extension) {
    if (!StringUtils.hasLength(extension)) {
      return name;
    }
    if (!StringUtils.hasLength(name)) {
      return extension;
    }
    int idx = name.lastIndexOf(DOT);
    if (idx > 0 && idx < name.length() - 1) {
      String ext = name.substring(idx + 1);
      if (extension.equalsIgnoreCase(ext)) {
        return name;
      }
    }
    return name + DOT + extension;
  }

  public static NacosDataParserHandler getInstance() {
    return ParserHandler.HANDLER;
  }

  private static class ParserHandler {

    private static final NacosDataParserHandler HANDLER = new NacosDataParserHandler();
  }
}
